import {Scene} from "./scene";
import {Camera} from "./camera";
import {Light} from "../lights/light";
import {ShaderProgram} from "./shaderProgram";
import {basic} from "../shaders/basic";
import {GameItem} from "../comps/gameItem";
import {mat4, vec3} from "gl-matrix";
import {BasicMaterial} from "../comps/basicMaterial";
import {LambertMaterial} from "../comps/lambertMaterial";
import {lambert} from "../shaders/lambert";
import {wireframe} from "../shaders/wireframe";
import {Material2} from "../comps/material2";

function isFunction(obj:any) {
    return Object.prototype.toString.call(obj) === '[object Function]';
}

export class Renderer2{

    tmpMat4 = mat4.create();
    bResize = false;
    programs = {};

    static _renderer:Renderer2 | null = null;

    //
    ambientC:vec3 = vec3.fromValues(0.2,0.2,0.2);
    directionalC = vec3.fromValues(0.8,0.8,0.8);
    directionalDir:vec3 = vec3.fromValues(1,1,0);

    constructor(public gl:WebGLRenderingContext,public width:number,public height:number){

        Renderer2._renderer = this;

        // 扩展
        console.log("=======extension start==============");
        // this.enableExt("ANGLE_instanced_arrays");
        this.enableExt("OES_vertex_array_object");
        // this.enableExt("OES_texture_float");
        // this.enableExt("OES_texture_half_float");
        console.log("=======extension end==============");


        gl.clearColor(0, 0, 0, 1);
        gl.clearDepth(1);
        gl.enable(gl.DEPTH_TEST);
        gl.depthFunc(gl.LEQUAL);

        // TODO: 后面根据材质去修改?
        // gl.enable(gl.BLEND);
        // gl.blendFunc(gl.ONE, gl.ONE);

        gl.cullFace(gl.BACK);
        gl.enable(gl.CULL_FACE);
        this.bResize = true;

        // 搞上一堆 shader,被引用
        this.initShaders();
    }

    static instance(){
        return Renderer2._renderer!;
    }

    enableExt(name:string){
        let gl = this.gl;
        let ext = gl.getExtension(name);
        if (!ext){
            throw `load extension ${name} failed!`;
        }
        for(let k in ext){
            // @ts-ignore
            if (isFunction(ext[k]) && !gl.hasOwnProperty(k)){
                // @ts-ignore
                gl[k] = ext[k].bind(ext);
                console.log('add ext method:',k);
            }
        }
    }

    private initShaders() {

        // basicMaterial
        let basicProgram = new ShaderProgram(this.gl,basic.vs,basic.fs);
        basicProgram.createAttrib("a_position");
        basicProgram.createUniform("projMatrix");
        basicProgram.createUniform("mvMatrix");
        basicProgram.createUniform("u_color");

        // @ts-ignore
        this.programs[BasicMaterial.ProgramName] = basicProgram;


        // lambertMaterial
        let lambertProgram = new ShaderProgram(this.gl,lambert.vs,lambert.fs);
        lambertProgram.createAttrib("a_position");
        lambertProgram.createAttrib("a_normal");
        lambertProgram.createUniform("enableLighting");
        lambertProgram.createUniform("ambientColor");
        lambertProgram.createUniform("directionalColor");
        lambertProgram.createUniform("lightingDirection");
        lambertProgram.createUniform("projMatrix");
        lambertProgram.createUniform("mvMatrix");
        lambertProgram.createUniform("normalMatrix");
        lambertProgram.createUniform("u_color");
        // @ts-ignore
        this.programs[LambertMaterial.ProgramName] = lambertProgram;


        // wireframe
        let wireframeProgram = new ShaderProgram(this.gl,wireframe.vs,wireframe.fs);
        wireframeProgram.createAttrib("a_position");
        wireframeProgram.createUniform("projMatrix");
        wireframeProgram.createUniform("mvMatrix");
        wireframeProgram.createUniform("u_color");
        // @ts-ignore
        this.programs[Material2.WireFrame] = wireframeProgram;
    }

    render(scene:Scene,camera:Camera){
        let gl = this.gl;
        if (this.bResize){
            gl.viewport(0,0,this.width,this.height);
            this.bResize = false;
        }

        gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

        //
        camera.updateMatrix();

        scene.objects.forEach(obj=>{
            obj.updateMatrix();
        });

        this.setupLights(scene.lights);
        this.renderScene(scene,camera);

        // this.renderSkybox();
        // this.renderGUI();
    }

    private setupLights(lights: Light[]) {
        // TODO: 电光源和聚光灯

    }

    private renderScene(scene: Scene,camera:Camera) {
        // TODO: fog/terrain/particle....
        //

        let programs = this.programs;
        let mvMatrix = this.tmpMat4;
        let objects = scene.objects;


        let gl = this.gl;

        // TODO: 优化方式:根据材质去分组
        for (let i = 0,ilen = objects.length; i < ilen; ++i){
            let obj = objects[i];
            if (obj instanceof GameItem){
                let material = obj.material;
                mat4.mul(mvMatrix,camera.viewMatrix,obj.matrix);

                if (material instanceof BasicMaterial){
                    // @ts-ignore
                    let program:ShaderProgram = programs[BasicMaterial.ProgramName];
                    program.bind();

                    program.setUniformMat4("projMatrix",camera.projMatrix);
                    program.setUniformMat4("mvMatrix",mvMatrix);
                    program.setUniformVec4("u_color",material.color.toVec4f());
                    material.draw(gl,camera.projMatrix,mvMatrix);
                    program.unbind();

                }else if(material instanceof LambertMaterial){
                    // @ts-ignore
                    let program: ShaderProgram = programs[LambertMaterial.ProgramName];
                    program.bind();

                    program.setUniformMat4("projMatrix",camera.projMatrix);
                    program.setUniformMat4("mvMatrix",mvMatrix);
                    program.setUniformVec4("u_color",material.color.toVec4f());
                    program.setUniformMat4("normalMatrix",obj.matrix);

                    // set lightinfo
                    program.setUniformI("enableLighting",1);
                    program.setUniformVec3("ambientColor",this.ambientC);
                    program.setUniformVec3("directionalColor",this.directionalC);
                    program.setUniformVec3("lightingDirection",this.directionalDir);

                    material.draw(gl,camera.projMatrix,mvMatrix);
                    program.unbind();
                }else{
                    // TODO: other materials
                }
            }
        }
    }

    getProgame(name: string) : ShaderProgram{
        // @ts-ignore
        return this.programs[name];
    }
}
